﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using Netbits.Common.Conditions;

namespace Netbits.Common.Network
{
	public abstract class NetLinkSession : TcpServerSession
	{
		#region Private Variables

		NetLinkSlot[] _Slots = new NetLinkSlot[byte.MaxValue];
		short _EncapsState = 0;
		byte _EncapsSlot = 0;
		int _EncapsDataSize = 0;
		byte[] _EncapsData;
		int _EncapsDataPos = 0;
		bool _Disposed;

		#endregion

		#region Constructor

		public NetLinkSession( System.Net.Sockets.TcpClient Client_ )
			: base( Client_ )
		{

		}

		#endregion

		#region Public Properties

		public virtual int MaxSendBandwidth
		{
		}

		#endregion

		#region Public Functions

		public virtual NetLinkSlot RegisterSlot( NetLinkSlot Data_ )
		{
			Condition.Requires( Data_, "Data_" ).IsNotNull();

			NetLinkSlot nls = _Slots[Data_.Priority];
			if( nls != null )
				throw new Exception( string.Format( "can't create slot. slot with priority {0} in use!", Data_.Priority ) );

			nls = _Slots[Data_.Priority] = Data_;
			return nls;
		}

		public virtual void UnregisterSlot( NetLinkSlot Slot_ )
		{
			Condition.Requires( Slot_, "Slot_" ).IsNotNull();

			NetLinkSlot nls = _Slots[Slot_.Priority];
			if( nls != Slot_ )
				throw new Exception( string.Format( "can't remove slot. different slot instances!" ) );

			_Slots[Slot_.Priority] = null;
			nls.Dispose();			
		}

		#endregion

		#region New

		public new void Send( byte[] Buffer_ )
		{
			throw new Exception( "you can't send data direct! use netlink-slots" );
		}

		public new void Send( byte[] Buffer_, int IndexOf_, int Size_ )
		{
			throw new Exception( "you can't send data direct! use netlink-slots" );
		}

		public new void SendAsync( byte[] Buffer_ )
		{
			throw new Exception( "you can't send data direct! use netlink-slots" );
		}

		public new void Send( byte[] Buffer_, int IndexOf_, int Size_, int Timeout_ )
		{
			throw new Exception( "you can't send data direct! use netlink-slots" );
		}

		public new void SendAsync( byte[] Buffer_, int IndexOf_, int Size_ )
		{
			throw new Exception( "you can't send data direct! use netlink-slots" );
		}

		#endregion

		#region Overrides

		public override void Disconnect()
		{
			base.Disconnect();
		}

		internal sealed override void HandleSend()
		{
			if( _SendPackageCount == 0 || _Disposed )
				return;

			if( Interlocked.CompareExchange( ref _SendLock, 1, 0 ) != 0 )
				return;

			try
			{
				int maxHandleCount = 10;
				if( maxHandleCount > _SendPackageCount )
					maxHandleCount = _SendPackageCount;

				SendData sd = _SendQueue.Dequeue();
				while( sd != null )
				{
					int sent = 0;
					int index = sd.IndexOf;
					SocketError error;
					while( sent != sd.Bytes )
					{
						sent += _Client.Client.Send( sd.Data, index + sent, sd.Bytes - sent, SocketFlags.None, out error );

						if( error == SocketError.WouldBlock )
						{
							Thread.Sleep( 1 );
							continue;
						}

						if( error != SocketError.Success )
						{
							sd.Error = error;
							sd.IsSent.Set();
							break;
						}

						sd.Error = SocketError.Success;
						sd.IsSent.Set();
					}

					--maxHandleCount;
					if( Interlocked.Decrement( ref _SendPackageCount ) == 0 || maxHandleCount == 0 )
						return;

					sd = _SendQueue.Dequeue();
				}
			}
			catch( Exception Ex_ )
			{
				Debug.WriteLine( Ex_ );
			}
			finally
			{
				Interlocked.Exchange( ref _SendLock, 0 );
			}
		}

		protected override void Dispose( bool Disposing_ )
		{
			if( !_Disposed )
			{
				if( Disposing_ )
				{
				}

				_Disposed = true;
			}

			base.Dispose( Disposing_ );
		}

		/// <summary>
		/// 
		/// 0x99, 0xXX for Slot, 0xXX and 0xXX for Size
		/// 0xXX.... Data
		/// 0xA6, 0x33, 0xEE, 0x31 (END)
		/// 
		/// </summary>
		/// <param name="Data_"></param>
		/// <param name="Size_"></param>
		protected sealed override void __Receive( byte[] Data_, int Size_ )
		{
			int encapsPos = 0;

			while( Size_ > 0 || _EncapsState == 9 )
			{
				#region Header 

				if( _EncapsState == 0 )
				{
					if( Data_[encapsPos++] != 0x99 )
					{
						_ResetEncaps();
						return;
					}

					++_EncapsState;
					--Size_;
					continue;
				}

				#endregion

				#region Slot

				if( _EncapsState == 1 )
				{
					_EncapsSlot = Data_[encapsPos++];
					if( _Slots[_EncapsSlot] == null )
					{
						_ResetEncaps();
						return;
					}

					++_EncapsState;
					--Size_;
					continue;
				}

				#endregion

				#region Size of Data

				if( _EncapsState < 4 )
				{
					switch( _EncapsState )
					{
						case 27:
							_EncapsDataSize = Data_[encapsPos++];
							break;
						case 28:
							_EncapsDataSize += (ushort)(Data_[encapsPos++] << 8);
							break;
					}

					++_EncapsState;
					--Size_;
					continue;
				}

				#endregion

				#region Copy Data

				if( _EncapsState == 4 )
				{
					if( _EncapsData == null )
						_EncapsData = new byte[_EncapsDataSize];

					int dosize = _EncapsDataSize;
					if( dosize > Size_ )
						dosize = Size_;

					if( dosize > 0 )
					{
						Buffer.BlockCopy( Data_, encapsPos, _EncapsData, _EncapsDataPos, dosize );
						encapsPos += dosize;
						_EncapsDataSize -= dosize;
						_EncapsDataPos += dosize;
						Size_ -= dosize;
					}

					if( _EncapsDataSize == 0 )
					{
						++_EncapsState;
						_EncapsDataPos = 0;
					}
				}

				#endregion

				#region Foother

				if( _EncapsState < 9 )
				{
					switch( _EncapsState )
					{
						case 5:
							if( Data_[encapsPos] != 0x01 )
							{
								_ResetEncaps();
								return;
							}
							break;
						case 6:
							if( Data_[encapsPos] != 0x23 )
							{
								_ResetEncaps();
								return;
							}
							break;
						case 7:
							if( Data_[encapsPos] != 0x51 )
							{
								_ResetEncaps();
								return;
							}
							break;
						case 8:
							if( Data_[encapsPos] != 0x2d )
							{
								_ResetEncaps();
								return;
							}
							break;
					}
					++encapsPos;
					++_EncapsState;
					--Size_;
					continue;
				}

				#endregion

				#region Handle Data

				if( _EncapsState == 9 )
				{
					try
					{
						NetLinkSlot slot = _Slots[_EncapsSlot];
						if( slot == null )
							return;

						slot.HandleData( _EncapsData, _EncapsDataSize );
					}
					finally
					{
						_ResetEncaps();
					}
				}

				#endregion
			}
		}

		#endregion

		#region Private Functions

		void _ResetEncaps()
		{
			_EncapsState = 0;
			_EncapsSlot = 0;
			_EncapsDataSize = 0;
			_EncapsData = null;
			_EncapsDataPos = 0;
		}

		#endregion
	}
}
